本章節將把讀取的影像進行處理如灰階、分別擷取R, G, B通道、針對影像通道減少顏色操作,最後把顯示的影像做保存,本篇依照上一篇[VS] C# - [01] EmguCV 影像讀取的程式繼續操作。
介面的部份把各轉換的方式分成不同的按鈕進行使用。
圖一 主程式介面
在轉換影像的時候,很多按鈕處理的方式大同小異,因此我們會把重複性高的程式做一個函數,使用該函數只要丟進影像與轉換類型即可獲得預期輸出,使用字串的方式傳遞會容易使錯誤,所以採用 enum
(列舉)的方式建立我們所需要的類型。
public enum EColorType
{
Blue = 0,
Green,
Red,
Gray
}
我們接取的影像型態為Image<Bgr, Byte>
,經過轉換灰階或者是單獨擷取通道的時候皆為一個通道,因此在輸出的影像時候則為Image<Gray, Byte>
的型態。
雖說接下來在按鈕按下的時候會先檢測影像是否存在,但為了安全起見,還是會從函式裡面做一個檢測影像是否為 null ,若為 null 透過 throw new
的方式建立我們所需要擲出的例外(Exception)資訊。
我們輸入的影像型態為 Bgr
的格式,因此我們使用 a_bitmap[index] 當中的 index 位置做為通道如 [0] = B, [1] = G, [2] = R,因此我們只要將通道擷取出來即可完成我們要提取的通道。
把 Bgr
的格式轉換成 Gray
的方式也只需要透過Convert<TColor, TDepth>
方式進行轉換。
private Image<Gray, Byte> ChanneConvert1(Image<Bgr, Byte> a_bitmap, EColorType a_type)
{
if (a_bitmap == null)
{
throw new NullReferenceException("影像不得為空。");
}
Image<Gray, Byte> convertImage;
// 顏色轉換
switch (a_type)
{
case EColorType.Blue:
convertImage = a_bitmap[0].Clone();
break;
case EColorType.Green:
convertImage = a_bitmap[1].Clone();
break;
case EColorType.Red:
convertImage = a_bitmap[2].Clone();
break;
case EColorType.Gray:
convertImage = a_bitmap.Convert<Gray, Byte>().Clone();
break;
default:
throw new NullReferenceException("無其他類型。");
}
return convertImage;
}
若不想要影像進來時因為擲出例外使程式中斷,可以採用回傳空影像(預設影像)的方式並帶著錯誤的資訊顯示給使用者,下面的方法則是將影像直接做一個 new Image<Gray, Byte>
並在每個像素預設為 Gray(255) ,也就是預設白影像。
convertImage = new Image<Gray, Byte>(m_image.Width, m_image.Height, new Gray(255));
MessageBox.Show("影像顯示錯誤。", "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
這邊保留顏色指的是使用原圖pixelA=(233, 50, 30)
與遮罩(0, 255, 255)
進行相減保留藍色的像素,最後會變成pixelA=(233, 0, 0)
,當然你也可以用圖片作為參考遮罩,也許該稱為濾鏡比較適當。
private Image<Bgr, Byte> ChannelReduce1(Image<Bgr, Byte> a_bitmap, EColorType a_type)
{
if (a_bitmap == null)
{
throw new NullReferenceException("影像不得為空。");
}
Image<Bgr, Byte> convertImage;
// 顏色轉換
switch (a_type)
{
case EColorType.Blue:
convertImage = a_bitmap.Sub(new Bgr(0, 255, 255)).Clone();
break;
case EColorType.Green:
convertImage = a_bitmap.Sub(new Bgr(255, 0, 255)).Clone();
break;
case EColorType.Red:
convertImage = a_bitmap.Sub(new Bgr(255, 255, 0)).Clone();
break;
default:
throw new NullReferenceException("無其他類型。");
}
return convertImage;
}
Image<TColor, TDepth>
格式眾多,為了方便這邊直接使用 Bitmap
的方式進行影像保存,只要使用 Save()
的函式並帶上路徑與檔案名稱就可以完成保存,為了讓開發者了解影像是否保存成功,使用 bool
的方式來進行響應,因路徑或者是檔名可能會有錯誤,因此採用 try catch
的方式掌控。
private bool SaveImage(Bitmap a_image, string a_path, string a_fileName)
{
try
{
a_image.Save($"{a_path}/{a_fileName}");
return true;
}
catch (Exception e)
{
MessageBox.Show(e.Message, "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
}
因大部分影像處理的功能皆為重複操作,因此只用按鈕第一個(藍)色作為顯示,只需要改變函式中的 EColorType.{名稱}
就可以對應各功能了。
灰階影像:
private void button2GrayImage_Click(object sender, EventArgs e)
{
if (m_image == null) return;
pictureBoxDisplayImage.Image = (Bitmap)ChanneConvert1(m_image, EColorType.Gray).ToBitmap().Clone();
pictureBoxDisplayImage.Invalidate();
}
藍:
private void buttonChannelBlue_Click(object sender, EventArgs e)
{
if (m_image == null) return;
pictureBoxDisplayImage.Image = (Bitmap)ChanneConvert1(m_image, EColorType.Blue).ToBitmap().Clone();
pictureBoxDisplayImage.Invalidate();
}
保留藍:
private void buttonKeepBlue_Click(object sender, EventArgs e)
{
if (m_image == null) return;
pictureBoxDisplayImage.Image = (Bitmap)ChannelReduce1(m_image, EColorType.Blue).ToBitmap().Clone();
pictureBoxDisplayImage.Invalidate();
}
影像保存:
private void buttonSaveImage_Click(object sender, EventArgs e)
{
// "./" 代表當前路徑
// 後面名稱需附上副檔名
if (SaveImage((Bitmap)pictureBoxDisplayImage.Image, "./", "save_image.bmp") == true)
{
MessageBox.Show("保存影像成功。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
MessageBox.Show("保存影像失敗。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
圖二 灰階影像
圖三 藍通道
圖四 透過遮罩顯示的保留藍
圖五 保存影像結果
EmguCV在影像的操作是非常的完整,在這邊你已經學會了如何把讀取影像進來的影像做簡易的處理,如整張影像灰階、提取通道、透過Sub遮罩減掉通道這些操作,下一篇我們將會學到 Panel 與 Dock 進行元件編排,以及使用 EmguCV 所提供的 ImageBox 元件取代 PictureBox。
Github: Link.